package com.buka.recipe.security;

import com.buka.recipe.security.handler.JsonLogoutSuccessHandler;
import com.buka.recipe.security.point.JsonAuthenticationEntryPoint;
import com.buka.recipe.security.strategy.JsonInvalidSessionStrategy;
import com.buka.recipe.security.strategy.JsonSessionInformationExpiredStrategy;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.session.HttpSessionEventPublisher;

@Log4j2
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {

    private final JsonAuthenticationSecurityConfig jsonAuthenticationSecurityConfig;

    @Autowired
    public WebSecurityConfig(JsonAuthenticationSecurityConfig jsonAuthenticationSecurityConfig) {
        this.jsonAuthenticationSecurityConfig = jsonAuthenticationSecurityConfig;
    }

    /**
     * http相关的配置，包括登入登出、异常处理、会话管理等
     * <p>
     * anonymous()：匿名访问：未登录可以访问，已登录不能访问
     * permitAll()：有没有认证都能访问：登录或未登录都能访问
     * denyAll(): 拒绝
     * authenticated()：认证之后才能访问
     * hasAuthority（）：包含权限
     * hasRole，hasAnyRole：是角色授权，授权代码需要加ROLE_前缀
     * hasAuthority，hasAnyAuthority：是权限授权，自定义的权限
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 禁用CORS
        http.cors(AbstractHttpConfigurer::disable);
        // 禁用CSRF
        http.csrf(AbstractHttpConfigurer::disable);
        // 禁用缓存
        http.headers(AbstractHttpConfigurer::disable);
        http.sessionManagement(session -> {
            // 会话固定攻击保护策略
            session.sessionFixation(
                    SessionManagementConfigurer.SessionFixationConfigurer::changeSessionId
            );
            session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
            session.sessionConcurrency(concurrency -> {
                concurrency.maximumSessions(1);
                concurrency.expiredSessionStrategy(new JsonSessionInformationExpiredStrategy());
            });

            session.invalidSessionStrategy(new JsonInvalidSessionStrategy());
        });
        // 授权请求
        http.authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/file/**", "/image/**", "/auth/*", "/upgrade/version/checkVersion", "/sms/send/captcha").permitAll()
                .anyRequest().authenticated());
        // 禁用form登录
        http.formLogin(AbstractHttpConfigurer::disable);
        // 登出成功
        http.logout(logoutCustomizer -> logoutCustomizer.logoutSuccessHandler(new JsonLogoutSuccessHandler()));
        http.exceptionHandling(exceptionHandlingCustomizer -> exceptionHandlingCustomizer.authenticationEntryPoint(new JsonAuthenticationEntryPoint()));

        http.apply(jsonAuthenticationSecurityConfig);

        return http.build();
    }

    /**
     * 监听session创建及销毁事件，来及时清理session的记录，以确保最新的session状态可以被及时感知到。
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * 密码机密处理器
     * <p>
     * 将BCryptPasswordEncoder对象注入到spring容器中,更换掉原来的 PasswordEncoder加密方式
     * 原PasswordEncoder密码格式为：{id}password。它会根据id去判断密码的加密方式。
     * 如果没替换原来的加密方式，数据库中想用明文密码做测试，将密码字段改为{noop}123456这样的格式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
